#include "mpi.h"
#include "Solver.h"

Solver :: Solver(Configurator* configurator, int argc, char** argv)
{
	MPI_Init(&argc, &argv);
	MPI_Comm_size(MPI_COMM_WORLD, &commsize);
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);

	threadBounds = (int*) malloc (sizeof(int)*(commsize + 1));
	if(rank == root)
	{
		dwarfConfigurator = configurator;					// process settings from the command line 
		dwarfConfigurator -> WriteSettings();
		dwarfConfigurator -> GetContent(&mainContent, &contentSize, &patternContent, &stateDimensionCoincidence);

		ConstructStates();
		InitParallelization(mainContent, contentSize);
	}

	MPI_Bcast(
		&contentSize,
		1,
		MPI_INT,
		root,
		MPI_COMM_WORLD);

	MPI_Bcast(
		&stateDimensionCoincidence,
		1,
		MPI_INT,
		root,
		MPI_COMM_WORLD);
		
	if(rank != root)
	{
		states = (int**) malloc (sizeof(int*)*stateDimensionChars);				// new states array
		for (int i = 0; i < stateDimensionChars; i++)
		{
			states[i] = (int*) malloc (sizeof(int)*(stateDimensionCoincidence + 1));
		}
		mainContent = (char*) malloc (sizeof(char)*(contentSize));				// new string for content storing
	}

	MPI_Bcast(
		threadBounds,
		commsize + 1,
		MPI_INT,
		root,
		MPI_COMM_WORLD);

	MPI_Bcast(
		mainContent, 
		contentSize,
		MPI_CHAR,
		root,
		MPI_COMM_WORLD);
	
	for (int i = 0; i < stateDimensionChars; i++)
	{
		MPI_Bcast(
			states[i],
			stateDimensionCoincidence+1,
			MPI_INT,
			root,
			MPI_COMM_WORLD);
	}
}

void Solver::InitParallelization(char *contentChar, int contentSize)
{
	int interval = 1 + contentSize / commsize;   // partitioning the String content (same parts for all threads)
    int leftBoard = 0;
    int part = 0;
	threadBounds[0] = 0;
    while (part < commsize - 1)
    {
		int certainInterval = interval;
		int j = 0;
		while (j==0)
		{
			for (int i = 0; i < stateDimensionCoincidence; i++)
			{
				if (contentChar[leftBoard + certainInterval] == patternContent[i])
				{
					certainInterval++;
					break;
				}
				j++;
			}			
		}
		certainInterval++;
        part++;
        leftBoard += certainInterval;
        threadBounds[part] = leftBoard;
     }
     threadBounds[commsize] = contentSize;
}

void Solver::ConstructStates()
{
	states = (int**) malloc (sizeof(int*)*stateDimensionChars);				// new states array
	for (int i = 0; i < stateDimensionChars; i++)
	{
		states[i] = (int*) malloc (sizeof(int)*(stateDimensionCoincidence + 1));
	}

	for (int i = 0; i < stateDimensionCoincidence + 1; i++)
	{
		for (int j = 0; j < stateDimensionChars; j++)
		{
			states[j] [i] = 0;
		}
    }
    for (int i = 0; i < stateDimensionCoincidence; i++)
	{
		states[patternContent[0]] [i] = 1;				// all turns from any char to the the first char in pattern are "initial" (=1)
    }
	for (int i = 1; i < stateDimensionCoincidence; i++)
    {
		int currentState = states[patternContent[i]] [i];
        if (currentState > 0)							// cycle-in-pattern checker
        {
			states[patternContent[currentState]] [i + 1] = currentState + 1;    // if cycle found, add "cycle" state
        }
 		states[patternContent[i]] [i] = i + 1;			// all turns from i-char to the (i+1)-char are "next" (equals to i+1)
    } 
	free(patternContent);								// pattern won't be used later
}

void Solver::StatesProcessing(int beginPoint, int endPoint)
{						
	int state = 0;
	for (int i = beginPoint; i < endPoint; i++)
	{
		if (mainContent[i] > 0)
		{
			state = states[mainContent[i]][state];			// change the state
		}
		if (state == stateDimensionCoincidence)				// pattern found!
		{
			positions.push_back(i);						    // push to list
			state = 0;
		}
	}
}

void Solver::Solve()
{
	StatesProcessing(threadBounds[rank], threadBounds[rank+1]);			// search on the process string
	if (rank == 0)														// if root, collect results from each process
	{	
		MPI_Status status;
		for (int i = 1; i < commsize; i++)
		{
			int recCount;
			MPI_Recv(&recCount, 1, MPI_INT, i, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
			for (int j = 0; j < recCount; j++)
			{
				int newValue;
				MPI_Recv(&newValue, 1, MPI_INT, i, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
				positions.push_back(newValue);
			}
		}
	} 
	else											// not root: send results to root
	{
		int size = positions.size();
		MPI_Send(&size, 1, MPI_INT, root, root, MPI_COMM_WORLD);
		while(! positions.empty())
		{
			int pos = positions.front();
			MPI_Send(&pos, 1, MPI_INT, root, root, MPI_COMM_WORLD);
			positions.pop_front();	
		}
	}
    free(mainContent);
	positions.sort();
}


void Solver::Finish(double time)	// Problem results output
{
	if (rank == 0)
	{
		if (! positions.empty())
		{
			printf("\n\nTotal count of pattern entries: %d \n", positions.size());
		}
		else
		{
			printf("\n\nPattern not found        \n");
		}
		dwarfConfigurator -> Close(time, positions);
	}
	MPI_Finalize();
}